package com.muyu.common.core.utils.pay;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil;
import com.muyu.common.core.constant.Constants;
import com.muyu.common.core.constant.PayConstants;
import com.muyu.common.core.utils.DateUtil;
import com.muyu.common.core.utils.XMLUtils;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.wechat.pay.contrib.apache.httpclient.util.RsaCryptoUtil;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.*;

public class WxPayUtils {

    private static final Logger logger = LoggerFactory.getLogger(WxPayUtils.class);

    private static CloseableHttpClient httpClient = null;

    private static PrivateKey merchantPrivateKey = null;

    private static X509Certificate certificate = null;

    /**
     * 获取httpClient
     */
    public static void getHttpClient() throws Exception {

        merchantPrivateKey = PemUtil.loadPrivateKey(
            new FileInputStream(PayConstants.WX_FILE_PATH + PayConstants.WX_KEY_PEM));


        // 加载平台证书（mchId：商户号,mchSerialNo：商户证书序列号,apiV3Key：V3密钥）
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
            new WechatPay2Credentials(PayConstants.MCH_ID, new PrivateKeySigner(PayConstants.MCH_SERIAL_NO, merchantPrivateKey)), PayConstants.WX_V3_KEY.getBytes("utf-8"));
        // 初始化httpClient
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
            .withMerchant(PayConstants.MCH_ID, PayConstants.MCH_SERIAL_NO, merchantPrivateKey)
            .withValidator(new WechatPay2Validator(verifier));
        // 通过WechatPayHttpClientBuilder构造的HttpClient，会自动的处理签名和验签
        httpClient = builder.build();
    }

    /**
     * APP下单
     */
    public static CloseableHttpResponse appPlaceOrder(String attach, String description, String outTradeNo, String url, Integer amount, String appId) throws Exception {
        getHttpClient();
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/app");
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("mchid", PayConstants.MCH_ID)
            .put("out_trade_no", outTradeNo)
            .put("appid", appId)
            .put("description", description)
            .put("notify_url", url);
        //.put("attach", attach);
        rootNode.putObject("amount")
            .put("total", amount)
            .put("currency", "CNY");
        objectMapper.writeValue(bos, rootNode);
        httpPost.setEntity(new StringEntity(bos.toString(Constants.UTF8), Constants.UTF8));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        return response;
    }

    /**
     * 微信小程序下单
     * @param attach
     * @param description
     * @param outTradeNo
     * @param url
     * @param amount
     * @param appId
     * @param openId
     * @return
     * @throws Exception
     */
    public static CloseableHttpResponse miniPlaceOrder(String attach, String description, String outTradeNo, String url, Integer amount, String appId, String openId) throws Exception {
        getHttpClient();
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("mchid", PayConstants.MCH_ID)
            .put("appid", appId)
            .put("description", description)
            .put("notify_url", url)
            .put("out_trade_no", outTradeNo)
            .put("attach", attach);
        rootNode.putObject("amount")
            .put("total", amount);
        rootNode.putObject("payer")
            .put("openid", openId);
        rootNode.putObject("settle_info")
            .put("profit_sharing", true);
        objectMapper.writeValue(bos, rootNode);
        httpPost.setEntity(new StringEntity(bos.toString(Constants.UTF8), Constants.UTF8));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        return response;
    }


    /**
     * 小程序下单
     */
    public static CloseableHttpResponse miniPlaceOrder(String attach, String description, String outTradeNo, String url, Integer amount, String appId) throws Exception {
        getHttpClient();
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("mchid", PayConstants.MCH_ID)
            .put("appid", appId)
            .put("description", description)
            .put("notify_url", url)
            .put("out_trade_no", outTradeNo)
            .put("attach", attach);
        rootNode.putObject("amount")
            .put("total", amount);
        objectMapper.writeValue(bos, rootNode);
        httpPost.setEntity(new StringEntity(bos.toString(Constants.UTF8), Constants.UTF8));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        return response;
    }

    /**
     * Native下单
     */
    public static CloseableHttpResponse nativePlaceOrder(String attach, String description, String outTradeNo, String url, Integer amount, String appId, JSONArray goodsDetailList) throws Exception {
        getHttpClient();
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("mchid", PayConstants.MCH_ID)
            .put("appid", appId)
            .put("description", description)
            .put("notify_url", url)
            .put("out_trade_no", outTradeNo)
            .put("attach", attach)
            //.put("time_start", DateUtil.d2s(new Date(), "yyyy-MM-dd'T'HH:mm:ss'+08:00'"))
            .put("time_expire", DateUtil.d2s(DateUtil.addMin(new Date(), 30), "yyyy-MM-dd'T'HH:mm:ss'+08:00'"));
        rootNode.putObject("amount")
            .put("total", amount);
        rootNode.putObject("detail")
            .putPOJO("goods_detail",goodsDetailList);
        objectMapper.writeValue(bos, rootNode);
        httpPost.setEntity(new StringEntity(bos.toString(Constants.UTF8), Constants.UTF8));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        return response;
    }

    /**
     * 微信native单笔退款接口
     * @param out_refund_no
     * @param url
     * @param refund
     * @param reason
     * @param total
     * @return
     * @throws Exception
     */
    public static CloseableHttpResponse nativeRefunds(String outTradeNo, String out_refund_no, String url, Integer refund, String reason, Integer total, JSONArray goodsDetailList) throws Exception {
        getHttpClient();
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds");
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("out_trade_no", outTradeNo)
            .put("out_refund_no", out_refund_no)
            .put("notify_url", url);
        if(reason != null){
            rootNode.put("reason", reason);
        }
        rootNode.putObject("amount")
            .put("refund", refund)
            .put("total", total)
            .put("currency", "CNY");
        rootNode.putPOJO("goods_detail",goodsDetailList);
        objectMapper.writeValue(bos, rootNode);
        httpPost.setEntity(new StringEntity(bos.toString(Constants.UTF8), Constants.UTF8));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        return response;
    }

    /**
     * 付款码支付
     * @param authCode
     * @param body
     * @param outTradeNo
     * @param amount
     * @param appId
     * @param ip
     * @param details
     * @return
     * @throws Exception
     * 错误码参照https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1
     * 成功事例  result_code=SUCCESS
     * {transaction_id=4200002168202405148648268182, nonce_str=uLjbrY9RPf7xsQlT, bank_type=OTHERS, openid=oy0VK64RaQWojoRbCymw9MxSJdzU, sign=F2CA001D43FD57F520B943A38DD5A548, return_msg=OK, fee_type=CNY, mch_id=1614402059, cash_fee=1, out_trade_no=agdfgasdfadsfasdfaf, cash_fee_type=CNY, appid=wx2b1d8fac5ff81f24, total_fee=1, trade_type=MICROPAY, result_code=SUCCESS, attach=, time_end=20240514162356, is_subscribe=N, return_code=SUCCESS}
     */
    public static Map<String, String> fukuanma(String authCode, String body, String outTradeNo, Integer amount, String appId, String ip, JSONArray details) throws Exception {
        WxPayConfigImpl config = new WxPayConfigImpl();
        config.setAppId(appId);
        String url = "https://api.mch.weixin.qq.com/pay/micropay";
        SortedMap<String, String> paramMap = new TreeMap<String, String>();
        paramMap.put("appid", appId);
        paramMap.put("mch_id", PayConstants.MCH_ID);
        paramMap.put("nonce_str", WXPayUtil.generateNonceStr());
        paramMap.put("body", body);
        paramMap.put("out_trade_no", outTradeNo);
        paramMap.put("total_fee", amount.toString());
        paramMap.put("spbill_create_ip", ip);
        paramMap.put("auth_code", authCode);
        paramMap.put("detail", details.toJSONString());
        String sign = WXPayUtil.generateSignature(paramMap, config.getKey(), WXPayConstants.SignType.MD5);
        paramMap.put("sign", sign);
        WXPay pay = new WXPay(config);
        String xmlStr = pay.requestWithoutCert(url, paramMap, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs());
        //调用解析xml工具
        return XMLUtils.getMapFromXML(xmlStr);
    }

    public static void main(String[] args) throws Exception {
//        JSONArray jsonArray = new JSONArray();
//        for (int i = 0; i < 2; i++) {
//            JSONObject jsonObject = new JSONObject();
//            jsonObject.put("goods_name",i);
//            jsonObject.put("goods_id",i);
//            jsonObject.put("quantity",i);
//            jsonObject.put("price",i);
//            jsonArray.add(jsonObject);
//        }
//        Map<String, String> map = fukuanma("132415149319711057", "ces", "23asas23argfadfg34", 1, PayConstants.APP_ID, "60.210.244.144", jsonArray);
//        System.out.println(map.toString());

        CloseableHttpResponse response = appQueryOrder("23asas23argfadfg34");
        JSONObject wxQueryResult = JSONObject.parseObject(EntityUtils.toString(response.getEntity()));
        System.out.println(wxQueryResult.toJSONString());
    }

    /**
     * APP查询订单（商户订单号）
     */
    public static CloseableHttpResponse appQueryOrder(String outTradeNo) throws Exception {
        getHttpClient();
        HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + outTradeNo + "?mchid=" + PayConstants.MCH_ID);
        httpGet.addHeader("Accept", "*/*");
        CloseableHttpResponse response = httpClient.execute(httpGet);
        return response;
    }

    /**
     * APP关闭订单
     */
    public static CloseableHttpResponse appCloseOrder(String outTradeNo) throws Exception {
        getHttpClient();
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        HttpPost httpPost = new HttpPost(" https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + outTradeNo + "/close");
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("mchid", PayConstants.MCH_ID);
        objectMapper.writeValue(bos, rootNode);
        httpPost.setEntity(new StringEntity(bos.toString(Constants.UTF8), Constants.UTF8));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        return response;
    }

    /**
     * 微信支付提现接口
     * @param batchRemark
     * @param batchName
     * @param outBatchNo
     * @param outDetailNo
     * @param totalAmount
     * @param appId
     * @param openid
     * @param userName
     * @return
     * @throws Exception
     */
    public static CloseableHttpResponse appTransfer(String batchRemark, String batchName, String outBatchNo, String outDetailNo, Integer totalAmount, String appId, String openid, String userName) throws Exception {
        getHttpClient();
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/transfer/batches");
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("appid", appId)
            .put("out_batch_no", outBatchNo)
            .put("batch_name", batchName)
            .put("batch_remark", batchRemark)
            .put("total_amount", totalAmount)
            .put("total_num", 1);
        rootNode.putArray("transfer_detail_list")
            .addObject()
            .put("out_detail_no", outDetailNo)
            .put("transfer_amount", totalAmount)
            .put("transfer_remark", batchRemark)
            .put("openid", openid)
            .put("user_name", RsaCryptoUtil.encryptOAEP(userName, certificate));
        objectMapper.writeValue(bos, rootNode);
        httpPost.addHeader("Wechatpay-Serial", PayConstants.WX_SERIAL_NO);
        httpPost.setEntity(new StringEntity(bos.toString(Constants.UTF8), Constants.UTF8));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        return response;
    }

    /**
     * 微信支付退款接口
     * @param out_refund_no
     * @param url
     * @param refund
     * @param reason
     * @param total
     * @return
     * @throws Exception
     */
    public static CloseableHttpResponse refunds(String outTradeNo, String out_refund_no, String url, Integer refund, String reason, Integer total) throws Exception {
        getHttpClient();
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds");
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("out_trade_no", outTradeNo)
            .put("out_refund_no", out_refund_no)
            .put("notify_url", url);
        if(reason != null){
            rootNode.put("reason", reason);
        }
        rootNode.putObject("amount")
            .put("refund", refund)
            .put("total", total)
            .put("currency", "CNY");
        objectMapper.writeValue(bos, rootNode);
        httpPost.setEntity(new StringEntity(bos.toString(Constants.UTF8), Constants.UTF8));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        return response;
    }

    /**
     * 微信支付-前端唤起支付参数
     *
     * @param prepayId 预下单接口返回数据 预支付交易会话标识	prepay_id
     * @return
     */
    public static JSONObject createMiniPayParams(String prepayId) {
        JSONObject result = new JSONObject();
        String nonceStr = RandomUtil.randomString(32);
        Long timestamp = System.currentTimeMillis() / 1000;
        String message = buildMiniMessage(timestamp, nonceStr, prepayId);
        String signature = null;
        try {
            signature = sign(message.getBytes("utf-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        result.put("appid", PayConstants.MINI_APP_ID);
        result.put("partnerid", PayConstants.MCH_ID);
        result.put("prepayid", prepayId);
        result.put("timestamp", timestamp.toString());
        result.put("noncestr", nonceStr);
        result.put("package", "prepay_id=" + prepayId);
        result.put("signType", "RSA");
        result.put("sign", signature);
        return result;
    }

    /**
     * 微信支付-前端唤起支付参数-构建签名参数
     * @param nonceStr 签名数据
     * @return
     */
    public static String buildMiniMessage(long timestamp, String nonceStr, String prepayId) {
        return PayConstants.MINI_APP_ID + "\n"
            + timestamp + "\n"
            + nonceStr + "\n"
            + "prepay_id=" + prepayId + "\n";
    }

    /**
     * 微信支付-前端唤起支付参数-签名
     * @param message 签名数据
     * @return
     */
    public static String sign(byte[] message) {
        try{
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(merchantPrivateKey);
            sign.update(message);
            return Base64.getEncoder().encodeToString(sign.sign());
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 用微信V3密钥解密响应体.
     *
     */
    public static String decryptResponseBody(String associatedData, String nonce, String ciphertext) {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

            SecretKeySpec key = new SecretKeySpec(PayConstants.WX_V3_KEY.getBytes(StandardCharsets.UTF_8), "AES");
            GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));

            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));

            byte[] bytes;
            try {
                bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));
            } catch (GeneralSecurityException e) {
                throw new IllegalArgumentException(e);
            }
            return new String(bytes, StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * 微信支付-前端唤起支付参数
     * @return
     */
    public static JSONObject createPayParams(String prepayId, String appid) {
        JSONObject result = new JSONObject();
        String nonceStr = RandomUtil.randomString(32);
        Long timestamp = System.currentTimeMillis() / 1000;
        String message = buildMessage(timestamp, nonceStr, prepayId, appid);
        String signature = null;
        try {
            signature = sign(message.getBytes("utf-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        result.put("appid", appid);
        result.put("partnerid", PayConstants.MCH_ID);
        result.put("prepayid", prepayId);
        result.put("timestamp", timestamp.toString());
        result.put("noncestr", nonceStr);
        result.put("package", "Sign=WXPay");
        result.put("signType", "RSA");
        result.put("sign", signature);
        return result;
    }

    /**
     * 微信支付-前端唤起支付参数-构建签名参数
     * @param nonceStr 签名数据
     * @return
     */
    public static String buildMessage(long timestamp, String nonceStr, String prepayId, String appid) {
        return appid + "\n"
            + timestamp + "\n"
            + nonceStr + "\n"
            + prepayId + "\n";
    }


}
